The ValueStack Explorer

Introduction

One of the most powerful parts of WebWork 2 and Struts 2 is the OGNL ValueStack, a stack which can be queried with OGNL expressions. Unfortuneatly enough, this very same part is the cause of much confusion amongst new users of the framework. There is a lot of information exposed to the view layer, but often it requires exploring the stack by a number of iterators. Testing expressions takes time, and sometimes a tiny typo can cause much grief.

What is it ?

The ValueStack Explorer is a combination of 2 tags (breakpoint and snapshot), a switch, and a monitoring client. The client allows you to send expressions to the breakpoint tag, which will block the current execution and send the response to the client. As such, it effectively operates as a breakpoint like any standard debugger.

How does it work ?

You start by starting the Switch - which is nothing more than a very simple message relay (=chat server). It accepts connections on a socket (by default it runs on port 4281). The client, created in Adobe Flash (should be ported to Java Swing - one day ..) connects to the Switch, can send OGNL statements and parses incoming (XML wrapped) responses. The breakpoint tag, when active, will open a socket and connect to the Switch as well, and execute any command against the OGNL Value Stack.

Installation

Download the first binary release here: http://wiki.opensymphony.com/download/attachments/8204/explorer-0.1.zip (source is coming, need to check Flash sources).

Unzip the explorer-x.x.zip file.

Place the explorer.jar file in your webapplication's WEB-INF/lib directory. Doubleclick it (or execute java -jar explorer.jar in a terminal) to start the switch.
Either run the client/explorer.swf file directly or open the client/explorer.html page. You should see the Flash client (make sure you have Flash 7+ plugin installed). It will connect by default to localhost on port 4281. If you want it to connect to another host, you'll have to provide the parameters yourself in the explorer.html file.
Open the explorer.html file in your favorite text editor, and change both explorer.swf?host=127.0.0.1&port=4281 (one for firefox/Opera, one for IE) references to whatever you want:

..
<param name="movie" value="explorer.swf?host=127.0.0.1&amp;port=4281" />
..
<embed src="explorer.swf?host=127.0.0.1&amp;port=4281" quality="high" bgcolor="#ffffff" 
width="1000" height="600" name="explorer" align="middle" allowScriptAccess="sameDomain" 
type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
..

Tutorial

Create a simple action called TestAction.

..
public class TestAction extends ActionSupport {

	private List list;

	public String execute() throws Exception {

		list = new ArrayList();
		list.add("Rene");
		list.add("Peter");
		list.add("Toby");
		list.add("Rainer");
		list.add("Patrick");

		return super.execute();
	}

	public List getList() {
		return list;
	}
}

Create a test page test.jsp which will display the list of names, and place it under the /WEB-INF/jsp/ directory.

<%@ taglib uri="/webwork" prefix="ww" %>
<%@ taglib uri="/vs" prefix="vs" %>

<html>
	<head>
		<title>Test Page</title>
		<ww:head />
	</head>

	<body>
		<vs:snapshot value="top.class" />
		<ww:iterator value="list" status="status">
			<li>
				<ww:property value="top"/>
				<vs:breakpoint active="true" name="personBP"/>
			</li>
		</ww:iterator>

		<vs:breakpoint active="true" name="finalBP" value="#context"/>

		<ww:iterator value="list" status="status">
			<li><ww:property value="top"/></li>
		</ww:iterator>
	</body>
</html>

Register the action and the result in xwork.xml

..
<action name="index" class="com.acme.test.TestAction">
	<result name="success" type="dispatcher">/WEB-INF/jsp/test.jsp</result>
</action>
..

Start the Switch if you already haven't done so (see Installation above)- it should start running on port 4281.

Visit the test action in your browser (preferably in another tab or window). It should render until you see about this:

  • Rene (BP PersonBP)

It should keep loading (well, at least in FireFox). Now, when you check your Switch log you should see two connections - one from your Flash client, and one from the first breakpoint tag. If you can't see this, verify that the Switch is running, your Flash client is running (reconnect using the menu if necessary), and you're not dealing with a firewall or an already occupied port).

Exploring the stack

Let's examine the Flash client here for a moment. What you see is the result of a very first command sent by the breakpoint, to the Switch, which relayed it to the Flash client, which in turned transformed the XML into a tree.
We can see the name of the breakpoint (personBP), we can see the command (top), and we can see the result: a single object with a toString() method which resulted in 'Rene'. That makes sense, because if we look at our TestAction, we can see we've got a list of names, starting with Rene, and in the jsp we're iterating those names, and we have a breakpoint on every iteration. Now, we see "Rene" (normally followed by the classname between brackets, except when dealing with java.lang.String objects), and we can click on the arrow in front of "Rene" to make the tree expand, and show all the methods available to this object.

A full list will dropdown as soon as you click the arrow, and you can see every method, its return value and parameters. Now you can click every leaf in the three, and the tool will create the OGNL expression for you. If you click on "Rene", top will display in the input field below. Click on hashCode(), toString(), .. and it will result in top.hashCode() and top.toString(). When you're dealing with getters, Maps or Collections/Arrays, the client will provide you with the correct expression.

Let's try issueing a custom command. Click on the input field below (besides the 'Query' button), and type #context, followed by a click on the Query button, or an <Enter>. A big list of objects will dropdown. These are all the objects which can be retrieved with the command #context. In fact, this is nothing more than a Map with a lot of Map.Entry objects in it. Once again, you can use the arrow to see what methods are available, but keep in mind: these are the methods for Map.Entry !

Once again, clicking on the object is enough to create the OGNL expression, so let's click "com.opensymphony.xwork.ActionContext.locale" [java.util.HashMap$Entry], and in the input field below, you'll get #context['com.opensymphony.xwork.ActionContext.locale']. Click the Query button again, and you'll receive the answer: "en_US" [java.util.Locale] (or some other Locale). When you expand the node now, you'll see it has all the methods for java.util.Locale instead of the old Map.Entry methods ! For example, clicking the getLanguage() method will result in #context['com.opensymphony.xwork.ActionContext.locale'].language, and submitting that will get you "en" as a response, on which you can invoke more String methods, and so on, ad infinitum.

Now, this is nice, but there's more (of course, there's always more). There's a menu on top of the tree (Connection, Breakpoint, Stack, About), which we'll explore now. Connection is fairly obvious, it controls the connection to the Switch server, About just displays a mildly uninteresting popup, Breakpoint is something we'll talk about in a minute, which leaves us with .. Stack.

Stack simply contains a list of some common commands, including #context (which we used above), #request, #session, etc .. clicking on them will simply send the command and show the result. Please note that not all commands return something (some are just empty - for example, #session will be empty until you actually place something in it (yes, this tool does not bend reality nor logic).

Breakpoints

Now, let's go to the next breakpoint. Remember, this is the very first breakpoint, and we have place a breakpoint inside a loop of a list with 5 names. Click the > arrow on the right of the Query button. Within an instant, you will be greated by "Peter". Clicking the > arrow again will show the next entry, "Toby". Take a look again at the test page in our browser, and you'll see it actually has advanced 2 items as well.
The Breakpoint menu on top has the very same command, plus some others: 'Next' (which we used now), 'Skip Next' (which will skip the next breakpoint) and 'Skip All' (which will skip all of the breakpoints and just render the page - surpise, surpise). Choose 'Skip Next' from the Breakpoint menu and see how it indeed skips one breakpoint, displaying "Patrick" rather than "Rainer" (take a look at the rendered page in your browser, you'll see there is no (BP personBP) entry next to Rainer's name.

More commands

Of course, you can do more than just simple object retrieval. How about testing ? You'll often find yourself using if/else structures, so we can easily test them as well. Let's try top eq "Jason", and just like you expected (I hope, if not, back away from the computer), "false" will be the result of this statement (with a nice icon - thanks Eclipse !). Similary, top eq "Patrick" will return "true", as will top.substring(0,2) == "Pa". Great !

Use the next button (>) to proceed to our next breakpoint. You'll see the breakpoint name has changed to finalBP (check the jsp page, you'll see we used that name on our last breakpoint, outside the loop - if you can't find this in your jsp, you're one lousy copy/paster, and you should exchange your PC for a Mac immediatly), and we can see the #context result is being displayed again. This is because we actually specified value="#context" in our tag (it defaults to 'top' if it's not specified). Sending top again will display the Action class.
You can see it has a method getList(), so sending top.list will give us back our names list. Of course, nothing stops us from altering this list, so let's do that:

top.list.add("Claus"), press enter, and behold, a "true" tells us that we successfully added Claus to our list. A quick top.list indeed shows the name has been added. By the way, pressing the up key in the input field allows you to cycle through the used commands (a bit buggy atm though).

Snapshots

There is one last thing you might find interesting. There are snapshot tags that allow you to place anything on the stack (for comparison later on), which can be retrieved using #snapshots.

Closing thoughts

Since this was the last breakpoint, pressing next will cause the page to be fully rendered. When you take a look at it, you'll see that we have in fact our initial 5 names, wich visual breakpoint marks, and a secondary listing with the additional 'Claus' name added to it. When you reload the page, you'll get "Rene" again, and so on ..
I do suggest you take a look at the Tag reference; esp. the 'active' attribute is interesting (it allows for conditional breakpoints, thank you very much, and by default its value, when not specified, is equal to weither or not you have the WebWork development mode on. So keep this in mind.

Finally, I do realise that it's a bit awkward to use a Java daemon and a Flash client, and that we really should move to a single (preferably WebStart) Swing application instead. I just didn't have time for it (and I wanted to provide a quick popup in a webpage at first, so I guess one could say it's historically 'grown' ).

Tag reference

Breakpoint Tag

Name Required Default Type Description
name false net.vaultnet.explorer.Breakpoint.toString() String The name you wish to give to this breakpoint.
active false webwork.devMode Object/Boolean Weither or not this tag is active. Accepts an expression.
value false top Object/String Initial request that will be executed against the OGNL stack.
host false localhost String The hostname where the Switch server is running on.
port false 4281 int The port the Switch server is running on.

Snapshot Tag

Name Required Default Type Description
value false top Object/String The expression result you want to store in this snapshot.